/**
 * Copyright (c) 2010, iDA MediaFoundry
 * All rights reserved.
 */
package be.idamf.cortizone.units
{
	import be.idamf.flash.utils.deferExec;
	
	import flash.events.EventDispatcher;
	
	import mx.collections.ArrayCollection;
	import mx.events.CollectionEvent;
	import mx.events.CollectionEventKind;
	import mx.events.PropertyChangeEvent;
	
	/* TODO
	[Event(name="updateComplete", type="mx.events.FlexEvent")]
	[Event(name="conflict", type="flash.events.Event")]
	[Event(name="change", type="flash.events.Event")]
	*/	

	/**
	 * @author Jan Van Coppenolle
	 */
	public class CollectionUnit extends EventDispatcher implements IUnitOfWork
	{
		////////////////////////////////////////////////////////////////////////
		// Instance

		protected var changes:Array;
		protected var updatePending:Boolean;

		////////////////////////////////////////////////////////////////////////
		// Constructor

		public function CollectionUnit(collection:ArrayCollection = null)
		{
			this.collection = collection;
		}

		////////////////////////////////////////////////////////////////////////
		// Properties

		[Bindable]
		public var overwrite:Boolean = true;

		private var _isDirty:Boolean;

		[Bindable]
		/**
		 * State of the working copy.
		 */
		public function get isDirty():Boolean
		{
			return _isDirty;
		}

		private function set isDirty(value:Boolean):void
		{
			_isDirty = value;
		}

		private var _collection:ArrayCollection;

		[Bindable]
		/**
		 * The collection that is converted to a working copy.
		 */
		public function get collection():ArrayCollection
		{
			return _collection;
		}

		public function set collection(value:ArrayCollection):void
		{
			if (_collection)
				_collection.removeEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler);

			_collection = value;

			if (_collection)
				_collection.addEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler, false, 0, true);

			update();
		}

		private var _copy:ArrayCollection;

		[Bindable]
		/**
		 * The working copy, changes applied to this collection can be tracked.
		 */
		public function get copy():ArrayCollection
		{
			return _copy;
		}

		private function set copy(value:ArrayCollection):void
		{
			_copy = value;
		}

		////////////////////////////////////////////////////////////////////////
		// Methods

		/**
		 * Creates a new snapshot.
		 */
		protected function update():void
		{
			var copy:ArrayCollection = new ArrayCollection();
			var item:Object;

			for each (item in _collection)
			{
				if (item == null)
				{
					deferExec(this, update);
					return;
				}
			}

			for each (item in _collection)
			{
				copy.addItem(item);
				IEventDispatcher(item).removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, itemChangeHandler);
				IEventDispatcher(item).addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, itemChangeHandler, false, 0, true);
			}

			copy.addEventListener(CollectionEvent.COLLECTION_CHANGE, copyChangeHandler, false, 0, true);

			if (this.copy)
				this.copy.removeEventListener(CollectionEvent.COLLECTION_CHANGE, copyChangeHandler);

			this.copy = copy;

			changes = [];
			isDirty = false;
		}

		/**
		 * Applies the changes in the snapshot to the collection,
		 * after this operation both contain the same items.
		 */
		public function commit():void
		{
			var item:Object;
			var index:int = 0;

			if (!_collection)
			{
				return;
			}

			_collection.removeEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler);

			// add the new
			for each (item in _copy)
			{
				if (!_collection.contains(item))
				{
					_collection.addItem(item);
				}
			}

			// remove the old
			while (index < _collection.length)
			{
				item = _collection.getItemAt(index);
				if (!_copy.contains(item))
				{
					IEventDispatcher(item).removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, itemChangeHandler);
					_collection.removeItemAt(index);
				}
				else
				{
					++index;
				}
			}

			_collection.addEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler, false, 0, true);

			changes = [];
			isDirty = false;

			update();
		}


		/**
		 * Reverts the changes on the items AND resets the collection to its
		 * initial state (= a clone of the collection).
		 */
		public function rollback():void
		{
			var event:PropertyChangeEvent;

			while (changes.length != 0)
			{
				event = changes.pop();
				IEventDispatcher(event.source).removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, itemChangeHandler);
				event.source[event.property] = event.oldValue;
			}

			update();
		}

		public function addItem(item:Object):void
		{
			_copy.addItem(item);
		}

		public function removeItem(item:Object):Boolean
		{
			var index:int = _copy.getItemIndex(item);

			if (index == -1)
				return false;

			_copy.removeItemAt(index);

			return true;
		}

		////////////////////////////////////////////////////////////////////////
		// Handlers

		protected function collectionChangeHandler(event:CollectionEvent):void
		{
			var item:Object;

			switch (event.kind)
			{
				case CollectionEventKind.RESET:
					if (_isDirty && !overwrite)
						throw new Error("Conflict");
					else
						update();
					return;

				case CollectionEventKind.ADD:
					deferExec(this, update);
					return;

				case CollectionEventKind.REMOVE:
					deferExec(this, update);
					return;

				case CollectionEventKind.MOVE:
				case CollectionEventKind.REFRESH:
				case CollectionEventKind.REPLACE:
				case CollectionEventKind.UPDATE:
				default:
					return;
			}
		}

		protected function itemChangeHandler(event:PropertyChangeEvent):void
		{
			changes.push(event);
			isDirty = true;
		}

		protected function copyChangeHandler(event:CollectionEvent):void
		{
			isDirty = true;
		}
	}
}